package cn.xinfei.xdecision.common.service.enginex.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.xinfei.xdecision.common.model.component.ComponentVo;
import cn.xinfei.xdecision.common.model.datax.datamanage.v2.Variable;
import cn.xinfei.xdecision.common.model.datax.enums.EnumUtil;
import cn.xinfei.xdecision.common.model.datax.enums.VariableTypeEnum;
import cn.xinfei.xdecision.common.model.enginex.po.EngineAction;
import cn.xinfei.xdecision.common.model.enginex.po.EngineNode;
import cn.xinfei.xdecision.common.model.login.SessionManager;
import cn.xinfei.xdecision.common.service.datax.datamanage.v2.impl.VariableServiceImpl;
import cn.xinfei.xdecision.common.utils.constant.CommonConst;
import cn.xinfei.xdecision.common.utils.constant.enginex.EngineOperator;
import cn.xinfei.xdecision.common.utils.enums.enginex.ConditionValueTypeEnum;
import cn.xinfei.xdecision.common.utils.enums.enginex.NodeTypeEnum;
import cn.xinfei.xdecision.common.utils.exception.ApiException;
import cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum;
import cn.xinfei.xdecision.common.utils.util.BeanUtilsPlus;
import cn.xinfei.xdecision.common.utils.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum.NODE_JSON_PARSE_ERROR;
import static cn.xinfei.xdecision.common.utils.exception.util.ApiExceptionUtil.exception;

/**
 * Created by maying on 2023/04/25.
 **/
@Service
@Slf4j
public class NodeJsonParseService {
    @Resource
    private VariableServiceImpl variableService;

    /**
     * 1.nodeJson -> nodeScript
     * 2.nodeJson -> componentCodes
     * 3.nodeJson -> fieldCodes
     * 4.nodeJson -> engineAction
     */
    public JSONObject nodeJsonParser(EngineNode node) {
        NodeTypeEnum nodeTypeEnum = NodeTypeEnum.adapad(node.getNodeType());
        JSONObject parseResult = new JSONObject().fluentPut("nodeScript", node.getNodeJson());
        switch (nodeTypeEnum) {
            case RULE_BASE:
            case SCORECARD:
            case DECISION_TABLES:
            case DECISION_TREE:
                parseResult = converTerminationInfo(node);
                break;
            case CLASSIFY:
                parseResult = convertClassify(node);
                break;
            case SANDBOX:
                parseResult = convertSandBox(node);
                break;
            case DECISION:
                parseResult = convertDecision(node);
                break;
            case CHAMPION_CHALLENGE:
            case PARALLEL:
                parseResult = convertChampionChallenge(node);
                break;
            case ACTION:
                parseResult = convertAction(node);
                break;
            default:
                break;
        }
        log.info("nodeJson:{},parseResult:{}", node.getNodeJson(), JSON.toJSONString(parseResult));
        return parseResult;
    }

    public EngineNode removeNodeCodeFromNodeJson(EngineNode node, String removeNodeCode) {
        NodeTypeEnum nodeType = NodeTypeEnum.adapad(node.getNodeType());
        switch (nodeType) {
            case CLASSIFY: //分组节点
            case SANDBOX: //分流节点
            case PARALLEL: //并行节点
            case CHAMPION_CHALLENGE: //冠军挑战
                String nodeJson = node.getNodeJson();
                if (StringUtil.isValidStr(nodeJson)) {
                    nodeJson = nodeJson.replace(removeNodeCode, "");
                    log.info("[更新nodeJson]节点code:{},更新前:{},更新后:{}", node.getNodeCode(), node.getNodeJson(), nodeJson);
                    node.setNodeJson(nodeJson);
                    JSONObject parseResult = nodeJsonParser(node);
                    node.setNodeScript(parseResult.getString("nodeScript"));
                }
                break;
            default:
                break;
        }
        return node;
    }


    public EngineNode clearAllNextNodeCodeFromNodeJson(EngineNode node) {
        String nodeJson = node.getNodeJson();
        if (StringUtil.isBlank(nodeJson)) {
            return node;
        }
        NodeTypeEnum nodeType = NodeTypeEnum.adapad(node.getNodeType());
        switch (nodeType) {
            case CLASSIFY: //分组节点
                JSONObject nodeJsonObj = JSONObject.parseObject(node.getNodeJson());
                JSONArray conditions = nodeJsonObj.getJSONArray("conditions");//分组条件组
                if (conditions != null && !conditions.isEmpty()) {
                    conditions.forEach(o -> {
                        JSONObject condition = (JSONObject) JSONObject.toJSON(o);
                        condition.put("nextNode", "");
                    });
                }
                nodeJsonObj.put("conditions", conditions);
                node.setNodeJson(JSONObject.toJSONString(nodeJsonObj));
                break;
            case SANDBOX: //分流节点
            case CHAMPION_CHALLENGE: //冠军挑战者
            case PARALLEL: //并行节点
                JSONArray sandboxs = JSONArray.parseArray(node.getNodeJson());
                if (sandboxs != null && !sandboxs.isEmpty()) {
                    sandboxs.forEach(o -> {
                        JSONObject sandbox = (JSONObject) JSONObject.toJSON(o);
                        sandbox.put("nextNode", "");
                    });
                }
                node.setNodeJson(JSONObject.toJSONString(sandboxs));
                break;
            default:
                return node;
        }
        return node;
    }


    /**
     * 处理nodeJson中保存的终止条件，适用组件：规则集、评分卡、决策树、决策表
     * 1. 将终止条件中子表达式对象拼接成一个完整表达式字符串
     * 2. 表达式用到的变量解析并单独存储
     *
     * @param node
     */
    private JSONObject converTerminationInfo(EngineNode node) {
        if (StringUtil.isBlank(node.getNodeJson())) {
            return new JSONObject();
        }

        JSONObject nodeJson = JSONObject.parseObject(node.getNodeJson());
        JSONObject nodeScript = new JSONObject(nodeJson);
        Set<String> fieldCodes = new HashSet<>();

        JSONObject terminationInfo = nodeJson.getJSONObject("terminationInfo");//终止条件
        //1. 终止条件未配置，不处理
        if (terminationInfo != null) {
            JSONArray selectedRule = terminationInfo.getJSONArray("selectedRule");//已选规则
            if (selectedRule != null && !selectedRule.isEmpty()) {
                JSONArray conditions = terminationInfo.getJSONArray("conditions");//终止条件表达式组
                if (conditions != null && !conditions.isEmpty()) {
                    fieldCodes = getComponentFieldCodesFromCondition(conditions);//获取表达式依赖变量codes

                    List<Variable> variables = variableService.findVariablesByOrganId(fieldCodes, SessionManager.getLoginAccount().getOrganId());
                    Map<String, Integer> varCodeTypeMap = variables.stream().collect(Collectors.toMap(Variable::getVarCode, Variable::getVarValueType));

                    //4. 覆盖nodeJson.terminationInfo.conditions值，并新增fieldTypeMap，转换为nodeScript
                    JSONObject coverResult = coverConditions2Formula(conditions, varCodeTypeMap);
                    terminationInfo.putAll(coverResult);
                    terminationInfo.put("fieldTypeMap", varCodeTypeMap);
                    nodeScript.put("terminationInfo", terminationInfo);
                    //5. 获取节点终止条件、输出结果依赖的变量code列表
                    JSONObject output = terminationInfo.getJSONObject("output");
                    fieldCodes.addAll(getComponentFieldCodesFromCondition(new JSONArray().fluentAdd(output)));
                }
            }
        }

        JSONObject result = new JSONObject();
        result.put("componentCodes", getComponentCodes(nodeJson));//节点依赖的所有组件code
        result.put("nodeScript", JSONObject.toJSONString(nodeScript));//转换后的nodeScript
        result.put("fieldCodes", CollectionUtil.newArrayList(fieldCodes));//节点终止条件依赖的变量code
        return result;
    }

    /**
     * 处理分组节点中子表达式对象，对象拼接成一个完整表达式字符串
     *
     * @param node
     */
    private JSONObject convertClassify(EngineNode node) {
        if (StringUtil.isBlank(node.getNodeJson())) {
            return new JSONObject();
        }
        JSONObject nodeJson = JSONObject.parseObject(node.getNodeJson());

        JSONObject nodeScript = new JSONObject();
        Set<String> fieldCodes = new HashSet<>();

        JSONArray conditions = nodeJson.getJSONArray("conditions");//分组条件组
        JSONArray fields = nodeJson.getJSONArray("fields");//分组依赖的变量
        Map<String, Integer> fieldTypeMap = new HashMap<>();//分组依赖的变量-<fieldCode,fieldValueType>
        Set<String> nextNodes = new HashSet<>();
        if (conditions != null && !conditions.isEmpty()) {

            JSONArray newConditions = new JSONArray();

            for (int i = 0; i < conditions.size(); i++) {
                JSONObject condition = conditions.getJSONObject(i);//分组条件

                JSONArray formulas = condition.getJSONArray("formulas");//分组表达式组
                fieldCodes.addAll(getFieldCodesFromCondition(formulas));//表达式依赖的变量集合
                List<Variable> variables = variableService.findVariablesByOrganId(fieldCodes, SessionManager.getLoginAccount().getOrganId());
                Map<String, Integer> varCodeTypeMap = variables.stream().collect(Collectors.toMap(Variable::getVarCode, Variable::getVarValueType));//<fieldCode,fielValueType>
                JSONObject result = coverConditions2Formula(formulas, varCodeTypeMap);
                fieldTypeMap.putAll(varCodeTypeMap);

                String nextNode = condition.getString("nextNode");
                String groupName = condition.getString("groupName");

                JSONObject newCondition = new JSONObject();
                newCondition.put("groupName", groupName);
                newCondition.put("nextNode", nextNode);
                newCondition.put("formulas", result.getString("conditions"));
                newCondition.put("formulaScript", result.getString("conditions"));

                newConditions.add(newCondition);

                if (StringUtil.isValidStr(nextNode)) {
                    nextNodes.add(nextNode);
                }

            }
            nodeScript.put("conditions", newConditions);
            nodeScript.put("fields", fields);
            nodeScript.put("fieldTypeMap", fieldTypeMap);
        }

        JSONObject result = new JSONObject();
        result.put("nextNodeCodes", CollUtil.join(nextNodes, ","));
        result.put("nodeScript", JSONObject.toJSONString(nodeScript));
        result.put("fieldCodes", CollectionUtil.newArrayList(fieldCodes));
        return result;
    }

    /**
     * 分流节点nodeScript组装，增加了分流比例sum值
     */
    private JSONObject convertSandBox(EngineNode node) {

        if (StringUtil.isBlank(node.getNodeJson())) {
            return new JSONObject();
        }
        JSONArray nodeJson = JSONArray.parseArray(node.getNodeJson());
        Set<String> nextNodes = new HashSet<>();
        int size = nodeJson.size();
        int sum = 0;

        for (int i = 0; i < size; i++) {
            JSONObject sandBox = nodeJson.getJSONObject(i);
            sum += sandBox.getIntValue("proportion");
        }

        for (int i = 0; i < size; i++) {
            JSONObject sandBox = nodeJson.getJSONObject(i);
            sandBox.put("sum", sum);
            String nextNodeCode = sandBox.getString("nextNode");
            if (StringUtils.isNotEmpty(nextNodeCode)) {
                nextNodes.add(nextNodeCode);
            }
        }

        JSONObject result = new JSONObject();
        result.put("nodeScript", JSONObject.toJSONString(nodeJson));
        result.put("nextNodeCodes", CollUtil.join(nextNodes, ","));
        return result;
    }

    private JSONObject convertAction(EngineNode node) {
        if (StringUtil.isBlank(node.getNodeJson())) {
            return new JSONObject();
        }
        JSONObject nodeJson = JSON.parseObject(node.getNodeJson());
        EngineAction engineAction = BeanUtilsPlus.copyProperties(node, EngineAction::new);
        engineAction.setActionServer(nodeJson.getInteger("actionServer"));
        engineAction.setActionInterface(nodeJson.getString("actionInterface"));
        engineAction.setPostType(nodeJson.getInteger("postType"));
        JSONObject result = new JSONObject();
        result.put("engineAction", engineAction);
        return result;
    }

    private JSONObject convertChampionChallenge(EngineNode node) {
        if (StringUtil.isBlank(node.getNodeJson())) {
            return new JSONObject();
        }
        JSONArray nodeJson = JSONArray.parseArray(node.getNodeJson());
        Set<String> nextNodes = new HashSet<>();
        int size = nodeJson.size();
        for (int i = 0; i < size; i++) {
            JSONObject proportion = nodeJson.getJSONObject(i);
            String nextNodeCode = proportion.getString("nextNode");
            if (StringUtils.isNotEmpty(nextNodeCode)) {
                nextNodes.add(nextNodeCode);
            }
        }
        JSONObject result = new JSONObject();
        result.put("nodeScript", JSONObject.toJSONString(nodeJson));
        result.put("nextNodeCodes", CollUtil.join(nextNodes, ","));
        return result;
    }

    /**
     * 决策选项nodeScript组装
     *
     * @param node
     */
    private JSONObject convertDecision(EngineNode node) {
        if (StringUtil.isBlank(node.getNodeJson())) {
            return new JSONObject();
        }
        JSONObject nodeJson = JSONObject.parseObject(node.getNodeJson());
        JSONArray conditions = nodeJson.getJSONArray("conditions");

        JSONObject nodeScript = new JSONObject(nodeJson);
        Set<String> fieldCodes = new HashSet<>();
        Map<String, Integer> fieldTypeMap = new HashMap<>();//依赖的变量-<fieldCode,fieldValueType>
        if (conditions != null && !conditions.isEmpty()) {

            //输出变量
            JSONObject outputField = nodeJson.getJSONObject("output");
            if (outputField != null) {
                fieldCodes.add(outputField.getString("fieldCode"));
            }

            JSONArray newConditions = new JSONArray();
            for (int i = 0; i < conditions.size(); i++) {
                JSONObject condition = conditions.getJSONObject(i);
                JSONArray formulas = condition.getJSONArray("formula");
                fieldCodes.addAll(getFieldCodesFromCondition(formulas));
                List<Variable> variables = variableService.findVariablesByOrganId(fieldCodes, SessionManager.getLoginAccount().getOrganId());
                Map<String, Integer> varCodeTypeMap = variables.stream().collect(Collectors.toMap(Variable::getVarCode, Variable::getVarValueType));//<fieldCode,fielValueType>

                JSONObject result = coverConditions2Formula(formulas, varCodeTypeMap);
                fieldTypeMap.putAll(varCodeTypeMap);

                JSONObject newCondition = new JSONObject();
                newCondition.put("result", condition.get("result"));
                newCondition.put("resultKey", condition.get("resultKey"));
                newCondition.put("formula", result.getString("conditions"));
                newCondition.put("formulaScript", result.getString("conditions"));

                newConditions.add(newCondition);
            }

            nodeScript.put("fieldTypeMap", fieldTypeMap);
            nodeScript.put("conditions", newConditions);

        }

        JSONObject result = new JSONObject();
        result.put("nodeScript", JSONObject.toJSONString(nodeScript));
        result.put("fieldCodes", CollectionUtil.newArrayList(fieldCodes));
        return result;

    }


    /**
     * 将conditions转换成表达式字符串，并保存表达式用到的变量map<fieldCode,fieldType>
     *
     * @param conditions
     * @return
     */
    private JSONObject coverConditions2Formula(JSONArray conditions, Map<String, Integer> varCodeTypeMap) {
        StringBuilder formula = new StringBuilder();//转换后的表达式
        for (int i = 0; i < conditions.size(); i++) {
            JSONObject condition = conditions.getJSONObject(i);
            if (StringUtils.isBlank(condition.getString("operator"))) {
                continue;
            }
            Integer fieldValueType = varCodeTypeMap.get(condition.getString("fieldCode"));
            formula.append(convertCondition(condition, fieldValueType));
            // 最后一个不拼接
            if (i < conditions.size() - 1) {
                formula.append(condition.getString("relativeOperator"));
            }
        }

        JSONObject result = new JSONObject();
        result.put("conditions", formula.toString());
        result.put("formulaScript", formula.toString());
        return result;
    }

    /**
     * 返回示例：(#{fieldCode} >= 18)
     *
     * @param conditon
     * @return
     */
    private String convertCondition(JSONObject conditon, Integer fieldValueType) {
        String fieldCode = conditon.getString("fieldCode");
        String operator = conditon.getString("operator");
        Object value = conditon.get("value");
        int valueType = conditon.getInteger("valueType");
        if (fieldValueType == null) {
            throw exception(NODE_JSON_PARSE_ERROR, "存在不识别的变量:" + fieldCode + ",请至变量中心确认变量状态");
        }
        VariableTypeEnum variableTypeEnum = EnumUtil.fromCodeOrThrow(VariableTypeEnum.class, String.valueOf(fieldValueType));

        StringBuffer expression = new StringBuffer();

        expression.append(EngineOperator.OPERATOR_LEFT_PARENTHESES);  // "("
        switch (variableTypeEnum) {
            case INTEGER:
            case DOUBLE:
            case BOOLEAN:
                //(#{fieldCode}==1/1.0/true/false)  (#{fieldCode}!=1) (#{fieldCode}>1) (#{fieldCode}<1) (#{fieldCode}>=1) (#{fieldCode}<=1)
                expression.append(EngineOperator.OPERATOR_VARIABLE_LEFT + fieldCode + EngineOperator.OPERATOR_VARIABLE_RIGHT);
                expression.append(operator);
                expression.append(value);
                break;
            case STRING:
                //(equals(#{fieldCode},'A') (notEquals(#{fieldCode},'A') (contains(#{fieldCode},'A') (notContains(#{fieldCode},'A')
                operator = operator.replace("not equals", "notEquals")
                        .replace("not contains", "notContains");

                expression.append(operator);
                expression.append(EngineOperator.OPERATOR_LEFT_PARENTHESES);  // "("
                expression.append(EngineOperator.OPERATOR_VARIABLE_LEFT + fieldCode + EngineOperator.OPERATOR_VARIABLE_RIGHT);
                expression.append(CommonConst.SYMBOL_COMMA);  // ","
                expression.append("'" + value + "'");  // "'value'"
                expression.append(EngineOperator.OPERATOR_RIGHT_PARENTHESES);  // ")"
                break;
            default:
                break;
        }
        expression.append(EngineOperator.OPERATOR_RIGHT_PARENTHESES);  // ")"

        return expression.toString();
    }

    private Set<String> getFieldCodesFromCondition(JSONArray conditions) {
        Set<String> fieldCodes = new HashSet<>();
        if (conditions == null || conditions.isEmpty()) {
            return fieldCodes;
        }
        conditions.forEach(o -> {
            JSONObject condition = JSON.parseObject(JSON.toJSONString(o));
            addFieldCodes(fieldCodes, condition);
        });
        return fieldCodes;
    }

    private Set<String> getComponentFieldCodesFromCondition(JSONArray conditions) {
        Set<String> fieldCodes = new HashSet<>();
        if (conditions == null || conditions.isEmpty()) {
            return fieldCodes;
        }
        conditions.forEach(o -> {
            JSONObject condition = JSON.parseObject(JSON.toJSONString(o));
            addFieldCodes(fieldCodes, condition);
            JSONArray result = condition.getJSONArray("result");
            if (CollectionUtils.isNotEmpty(result)) {
                result.forEach(item -> addFieldCodes(fieldCodes, JSON.parseObject(JSON.toJSONString(item))));
            }
        });
        return fieldCodes;
    }

    private void addFieldCodes(Set<String> fieldCodes, JSONObject condition) {
        String fieldCode = condition.getString("fieldCode");
        Object value = condition.get("value");
        Integer valueType = condition.getInteger("valueType");
        if (valueType == null) {
            throw exception(NODE_JSON_PARSE_ERROR, "参数缺失:valueType");
        }

        fieldCodes.add(fieldCode);
        if (valueType == ConditionValueTypeEnum.VARIABLE.getCode()) {
            fieldCodes.add(String.valueOf(value));
        }
    }

    private List<ComponentVo> getComponentCodes(JSONObject nodeJson) {
        List<ComponentVo> componentVoList = new ArrayList<>();
        if (Objects.isNull(nodeJson)) {
            return componentVoList;
        }
        JSONArray componentList = nodeJson.getJSONArray("componentList");
        if (Objects.isNull(componentList)) {
            throw new ApiException(ErrorCodeEnum.NODE_JSON_ERROR.getCode(), ErrorCodeEnum.NODE_JSON_ERROR.getMessage());
        }
        componentList.forEach(jo -> {
            JSONObject componentObject = (JSONObject) jo;
            ComponentVo componentVo = new ComponentVo()
                    .setCode(componentObject.getString("code"))
                    .setVersionNo(componentObject.getLong("versionNo"));
            componentVoList.add(componentVo);
        });

        return componentVoList;
    }
}
